package Lexical;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import Lexical.Word;

import Lexical.Token;

import Util.BasicPatterns;
import Util.HashTable;

public class LexicalAnalyzer 
{	
	private HashTable reserved;							// reserved words
	private FileInputStream input;						// file
	private int character;								// current character
	private int line;									// current line
		
	/**
	 * Constructor for the LexicalAnalyzer class.
	 */
	public LexicalAnalyzer ()
	{
		// Initialize the line counter
		this.line = 0;
		
		// Store the reserved words of the language
		reserved = reserveWord();
	}
	
	/**
	 * Scans the source code for the next valid Token.
	 * @return
	 * A Token object for the token found.
	 * @throws IOException 
	 * If some IO exception occurs while reading the source code.
	 */
	public Token scan() throws IOException
	{		
		// Remove unnecessary things
		while (true)
		{
			// If first time, reads the first character
			if (this.line == 0)
			{
				this.character = input.read();		// get first character
				this.line = 1;
			}
						
			// Ignore white spaces, tabulation
			if (BasicPatterns.isWhiteSpace((char)this.character))
			{
				// Reads next character and goes to the beginning of the while-loop
				this.character = input.read();		// get next character
				continue;				
			}
			
			// Ignore carriage return
			else if ((char)this.character == '\r')
			{
				this.character = input.read();		// get next character
				continue;
			}
			
			// Newline (line feed)
			else if (BasicPatterns.isLineFeed((char)this.character))
			{
				this.line++;
				this.character = input.read();		// get next character		
			}
			
			// Ignore comment lines (after '#')
			else if (BasicPatterns.isComment((char)this.character))
			{
				// Jump to next line
				do
				{
					this.character = this.input.read();
				} while (!BasicPatterns.isLineFeed((char)this.character));					
				
				this.line++;
				this.character = input.read();		// get next character
			}
			
			else
				break;
		}
		
		// Logical Operators
		switch ((char)this.character) 
		{
			case '=':
				if ((char)(this.character = input.read()) == '=')
				{
					this.character = input.read();
					return new Word(Tag.RELOP, "==", this.line);
				}
				else
				{
					this.character = input.read();
					Word w = new Word(Tag.SYMBOL, "=", this.line);
					w.tag.setId(this.character);
					return w;
				}
			case '!':
				if ((char)(this.character = input.read()) == '=')
				{
					this.character = input.read();
					return new Word(Tag.RELOP, "!=", this.line);
				}
				else
				{
					this.character = input.read();
					Word w = new Word(Tag.SYMBOL, "!", this.line);
					w.tag.setId(this.character);
					return w;
				}
			case '<':
				if ((char)(this.character = input.read()) == '=')
				{
					this.character = input.read();
					return new Word(Tag.RELOP, "<=", this.line);
				}
				else
				{
					this.character = input.read();
					return new Word(Tag.RELOP, "<", this.line);
				}
			case '>':
				if ((char)(this.character = input.read()) == '=')
				{
					this.character = input.read();
					return new Word(Tag.RELOP, ">=", this.line);
				}
				else
				{
					this.character = input.read();
					return new Word(Tag.RELOP, ">", this.line);
				}
			case ':':
				if ((char)(this.character = input.read()) == '=')
				{
					this.character = input.read();
					Word w = new Word(Tag.SYMBOL, ":=", this.line);
					w.tag.setId(258);
					return w;
				}
				else
				{
					this.character = input.read();
					Word w = new Word(Tag.SYMBOL, ":", this.line);
					w.tag.setId(this.character);
					return w;
				}
		}
		
		// Arithmetic Operators
		switch ((char)this.character) 
		{
			case '*':
				this.character = input.read();
				return new Word(Tag.MULTDIVMOD, "*", this.line);
			case '/':
				this.character = input.read();
				return new Word(Tag.MULTDIVMOD, "/", this.line);
			case '%':
				this.character = input.read();
				return new Word(Tag.MULTDIVMOD, "%", this.line);				
			case '+':
				this.character = input.read();
				return new Word(Tag.SUMSUB, "+", this.line);
			case '-':
				this.character = input.read();
				return new Word(Tag.SUMSUB, "-", this.line);						
		}
		
		// Integer and Real numbers
		if (BasicPatterns.isNumber((char)this.character))
		{
			int v = 0;
			do 
			{
				v = 10 * v + java.lang.Integer.parseInt((String.valueOf((char)this.character)));
				this.character = input.read();
			} while (BasicPatterns.isNumber((char)this.character));
			
			if ((char)this.character != '.')
				return new Integer(v, this.line);
			
			double x = v;
			double d = 10;
			while (true) 
			{
				this.character = input.read();
				if (!BasicPatterns.isNumber((char)this.character))
					break;
				
				x = x + java.lang.Integer.parseInt((String.valueOf((char)this.character))) / d;
				d = d * 10;
			}
			return new Real(x, this.line);
		}
		
		// Identifiers
		if (BasicPatterns.isLetter_((char)this.character))
		{
			StringBuffer buffer = new StringBuffer();
			do 
	        {
	            buffer.append((char)this.character);
	            this.character = input.read();
	        } while(BasicPatterns.isLetter_((char)this.character) || BasicPatterns.isNumber((char)this.character));
			
			String str = buffer.toString().toLowerCase();
			
			// If TypeTag word
			for (TypeTag typetag : TypeTag.values())
			{
				if (typetag.getName().toLowerCase().equals(str))
					return new Type (typetag, this.line);
			}
	        
	        Word w = (Word)reserved.getValue(str);	        
	        // If reserved word
	        if (w != null)
	        {
	        	w.line = this.line;
	        	return w;
	        }
	                
	        else
	        {
	        	return new Word (Tag.IDENTIFIER, str, this.line);
	        }	         
		}
		
		// String literal
		if ((char)this.character == '"')
		{
			StringBuffer buffer = new StringBuffer();
			this.character = input.read();
			while ((char)this.character != '"') 
			{
				buffer.append((char)this.character);
				this.character = input.read();
			}
			this.character = input.read();
				         
	        return new Word (Tag.STRINGLITERAL, buffer.toString(), this.line);									
		}
		
		// Anything else 
		if (this.character == -1)
			return null;
		else
		{
			Word w = new Word (Tag.SYMBOL, String.valueOf((char)this.character), this.line);
			w.tag.setId(this.character);
			this.character = ' ';
			return w;			
		}	
	}
		
	/**
	 * Adds the reserved words of the language to a HashTable
	 */
	private HashTable reserveWord () 
	{  
		HashTable table = new HashTable();
		for (Tag tag : Tag.values())
		{
			if(tag.isReserved())
			{
				Word w = new Word (tag, tag.getName());
				table.put(w.value, w);
			}
		}
		
		return table;
	}
	
	/**
	 * Sets the file where the source code is.
	 * @param filename the name of the file.
	 */
	public void setFile (String filename)
	{
		try 
		{
			this.input = new FileInputStream(filename);
		} 
		catch (FileNotFoundException e) 
		{
			e.printStackTrace();
		} 
	}
	
	/**
	 * Closes the input stream of the source code file.
	 */
	public void closeFile()
	{
		try 
		{
			this.input.close();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) throws IOException
	{
		LexicalAnalyzer lex = new LexicalAnalyzer();
		lex.setFile("../compilador/src/main/resources/codigo.txt");
		
		System.out.println("               VALOR                 TIPO            ID       LINHA");
		System.out.println("-------------------------------------------------------------------");
		Token token;
		while ( (token = lex.scan()) != null) {
			System.out.printf("%20s %20s %13d %11d\n", token.toString(), token.tag, token.tag.getId(), token.line);
		}
		
		lex.closeFile();
	}
	
	public void analisar() throws IOException {
		System.out.println("               VALOR                 TIPO            ID       LINHA");
		System.out.println("-------------------------------------------------------------------");
		Token token;
		while ( (token = scan()) != null) {
			System.out.printf("%20s %20s %13d %11d\n", token.toString(), token.tag, token.tag.getId(), token.line);
		}
		
		closeFile();
	}
}
